﻿using System;
using System.Drawing;
using System.Web;
using Spring.Transaction.Interceptor;
using TSites.Common.Const;
using TSites.Common.Enums;
using TSites.Core.DataInterfaces.UserManagerment;
using TSites.Core.Domain.UserMangerment;
using TSites.Utility;
using TSites.Utility.Captcha;
using TSites.Utility.Encrypt;
using TSites.Utility.Random;

namespace TSites.UserMgmt
{
	public class AccountMembership : LoggerBase, IMembership
	{
		#region IMembership Properties

		public IUserDao UserDaoProvider { get; set; }
		// Encrypt
		public IEncryptation EncryptProvider { get; set; }
		// Random string for password
		public IRandom RandomProvider { get; set; }
		
		// captcha
		public IRandom RandomCaptchaProvider { get; set; }
		public AbstractCaptcha CaptchaProvider { get; set; }

		protected int _passwordLen = 8;

		#endregion

		#region IMembership Members

		public User GetByUserId(string userId)
		{
			return UserDaoProvider.GetByID(userId);
		}

		[Transaction]
        public RET_CODE RegisterUser(User entity)
        {
			// check UserId or Email are existed in case js is disabled
			RET_CODE retCode;
			retCode = CheckExists(entity.ID, entity.Email);
			if (retCode == RET_CODE.SUCCESS)
			{
				DateTime currentDate = DateTime.Now;
				// update some of fields
				string encryptPwd, salt;
				encryptPwd = EncryptProvider.ComputeHash(entity.Password, out salt);
				entity.Password = encryptPwd;
				entity.Salt = salt;

				entity.KeyVerify = EncryptProvider.ComputeHash(entity.ID, out salt); ;
				entity.KeyVerifyExpire = currentDate.Date.AddDays(3);
				entity.ActiveCode = (int)ACTIVE_CODE.MUST_ACTIVE_BY_EMAIL;
				entity.DeleteFlag = false;
				entity.CreateDate = currentDate;
				// Address
				foreach (Address add in entity.Addresses)
				{
					add.UserEntity = entity;
					add.CreateUserId = entity.CreateUserId;
					add.CreateDate = currentDate;
				}
				// Phone
				foreach (Phone phone in entity.Phones)
				{
					phone.UserEntity = entity;
					phone.CreateUserId = entity.CreateUserId;
					phone.CreateDate = currentDate;
				}

				try
				{
					UserDaoProvider.Insert(entity);

					// email
					// code ipmplement here ...

					retCode = RET_CODE.SUCCESS;
				}
				catch (Exception ex)
				{
					retCode = RET_CODE.DB_ERROR;
					Error(ex.Message, ex);
				}
			}

            return retCode;
        }

		[Transaction]
		public RET_CODE ValidateUser(string userId, string password, out User currentUser)
		{
			RET_CODE retCode = RET_CODE.ERROR;
			currentUser = null;
			try
			{
				// 1. get current user
				currentUser = GetByUserId(userId);
				if (currentUser == null)
					return RET_CODE.USERID_DOES_NOT_EXISTED;

				// 2. check active code
				ACTIVE_CODE activeCode = (ACTIVE_CODE)currentUser.ActiveCode;
				switch (activeCode)
				{
					case ACTIVE_CODE.ACTIVED:
						retCode = RET_CODE.SUCCESS;
						break;

					case ACTIVE_CODE.MUST_ACTIVE_BY_EMAIL:
						return RET_CODE.NOT_YET_ACTIVED;

					case ACTIVE_CODE.MUST_CHANGE_PASSWORD:
						retCode = RET_CODE.MUST_CHANGE_PASSWORD;
						break;

					case ACTIVE_CODE.LOCK:
						return RET_CODE.USER_LOCKED;

					default:
						return RET_CODE.ERROR;
				}

				// 3. check password
				if (!EncryptProvider.VerifyHash(password, currentUser.Salt, currentUser.Password))
					return RET_CODE.WRONG_PASSWORD;

				// 4. update User
				currentUser.LastLoginDate = DateTime.Now;
				UserDaoProvider.Update(currentUser);
			}
			catch (Exception ex)
			{
				retCode = RET_CODE.ERROR;
				Error(ex.Message, ex);
			}
			return retCode;
		}

		[Transaction]
		public RET_CODE Logon(string userId, string password)
		{
			RET_CODE retCode = RET_CODE.ERROR;
			try
			{
				// 1. get current user
				User currentUser = GetByUserId(userId);
				if (currentUser == null)
					return RET_CODE.USERID_DOES_NOT_EXISTED;

				// 2. check active code
				ACTIVE_CODE activeCode = (ACTIVE_CODE)currentUser.ActiveCode;
				switch (activeCode)
				{
					case ACTIVE_CODE.ACTIVED:
						retCode = RET_CODE.SUCCESS;
						break;

					case ACTIVE_CODE.MUST_ACTIVE_BY_EMAIL:
						return RET_CODE.NOT_YET_ACTIVED;

					case ACTIVE_CODE.MUST_CHANGE_PASSWORD:
						retCode = RET_CODE.MUST_CHANGE_PASSWORD;
						break;

					case ACTIVE_CODE.LOCK:
						return RET_CODE.USER_LOCKED;

					default:
						return RET_CODE.ERROR;
				}

				// 3. check password
				if (!EncryptProvider.VerifyHash(password, currentUser.Salt, currentUser.Password))
					return RET_CODE.WRONG_PASSWORD;

				// 4. update User
				currentUser.LastLoginDate = DateTime.Now;
				UserDaoProvider.Update(currentUser);

				// 5. save User in Session
				HttpContext.Current.Session[StringConst.USER_SESSION] = currentUser;
			}
			catch (Exception ex)
			{
				retCode = RET_CODE.ERROR;
				Error(ex.Message, ex);
			}
			return retCode;
		}

		[Transaction]
		public RET_CODE UpdateUser(User entity)
		{
			// check Email is used by another User in case of js is disabled
			RET_CODE retCode;
			retCode = EmailExists(entity.Email, entity.ID);
			if (retCode == RET_CODE.SUCCESS)
			{
				DateTime currentDate = DateTime.Now;
				// update some of fields
				entity.UpdateDate = currentDate;
				// Address
				foreach (Address add in entity.Addresses)
				{
					if (add.CreateUserId == null) // means Insert
					{
						add.CreateUserId = entity.ID;
						add.CreateDate = currentDate;
					}
					add.UpdateUserId = entity.ID;
					add.UpdateDate = currentDate;
				}
				// Phone
				foreach (Phone phone in entity.Phones) // means Insert
				{
					if (phone.CreateUserId == null)
					{
						phone.CreateUserId = entity.ID;
						phone.CreateDate = currentDate;
					}
					phone.UpdateUserId = entity.ID;
					phone.UpdateDate = currentDate;
				}

				try
				{
					UserDaoProvider.Update(entity);
					retCode = RET_CODE.SUCCESS;
				}
				catch (Exception ex)
				{
					Error(ex.Message, ex);
					retCode = RET_CODE.ERROR;
				}
			}

			return retCode;
		}

		public RET_CODE ChangePassword(string userId, string currentPassword, string newPassword)
		{
			// Get CurrentUser
			User currentUser = GetByUserId(userId);
			if (currentUser == null)
			{
				return RET_CODE.USERID_DOES_NOT_EXISTED;
			}
			// Check current password
			bool isMatch = EncryptProvider.VerifyHash(currentPassword, currentUser.Salt, currentUser.Password);
			if (!isMatch)
			{
				return RET_CODE.WRONG_PASSWORD;
			}
			// Encrypt new password
			string encryptPwd, salt;
			encryptPwd = EncryptProvider.ComputeHash(newPassword, out salt);
			
			// Update log
			if ((ACTIVE_CODE)currentUser.ActiveCode == ACTIVE_CODE.MUST_CHANGE_PASSWORD)
			{
				currentUser.ActiveCode = (int)ACTIVE_CODE.ACTIVED;
			}
			currentUser.UpdateDate = DateTime.Now;
			currentUser.UpdateUserId = userId;
			currentUser.Reason = "Change password";

			RET_CODE retCode = RET_CODE.ERROR;
			try
			{
				UserDaoProvider.Update(currentUser);
				retCode = RET_CODE.SUCCESS;
			}
			catch (Exception ex)
			{
				retCode = RET_CODE.DB_ERROR;
				Error(ex.Message, ex);
			}
			return retCode;
		}

		public RET_CODE ForgotPassword(string email)
		{
			User currentUser = UserDaoProvider.GetByEmail(email);
			if (currentUser == null)
				return RET_CODE.EMAIL_DOES_NOT_EXISTED;

			string salt, newPassword;
            newPassword = GeneratePassword();
			// email to user

			// update new generate password to DB
			string encryptPwd = EncryptProvider.ComputeHash(newPassword, out salt);
			currentUser.Password = encryptPwd;
			currentUser.Salt = salt;
			currentUser.ActiveCode = (int)ACTIVE_CODE.MUST_CHANGE_PASSWORD;
			currentUser.Reason = "Forgot password";

			RET_CODE retCode = RET_CODE.ERROR;
			try
			{
				UserDaoProvider.Update(currentUser);
				retCode = RET_CODE.SUCCESS;
			}
			catch (Exception ex)
			{
				retCode = RET_CODE.DB_ERROR;
				Error(ex.Message, ex);
			}

			return retCode;
		}

		public RET_CODE ActiveByKeyVerify(string keyVerify)
		{
			RET_CODE retCode;
			User currentUser = null;
			try
			{
				currentUser = UserDaoProvider.GetByKeyVerify(keyVerify);
				if (currentUser == null)
				{
					retCode = RET_CODE.INVALID_KEY_VERIFY;
				}
				else
				{
					if (DateTime.Today > currentUser.KeyVerifyExpire)
					{
						retCode = RET_CODE.KEY_VERIFY_IS_EXPIRED;
					}
					else
					{
						retCode = RET_CODE.SUCCESS;
					}
				}
			}
			catch (Exception ex)
			{
				retCode = RET_CODE.DB_ERROR;
				Error(ex.Message, ex);
			}
			return retCode;
		}

		public void Logout()
		{
			HttpContext.Current.Session.Clear();
			HttpContext.Current.Session.Abandon();
		}

		public RET_CODE CheckExists(string userId, string email)
		{
			RET_CODE retCode;
			try
			{
				retCode = UserDaoProvider.CheckExists(userId, email, EXISTS_TYPE.ALL) ? 
					RET_CODE.USERID_EMAIL_EXISTED : RET_CODE.SUCCESS;
			}
			catch (Exception ex)
			{
				Error(ex.Message, ex);
				retCode = RET_CODE.DB_ERROR;
			}
			return retCode;
		}

		public RET_CODE UserExists(string userId)
		{
			RET_CODE retCode;
			try
			{
				retCode = UserDaoProvider.CheckExists(userId, null, EXISTS_TYPE.USER_ID) ?
					RET_CODE.USERID_EXISTED : RET_CODE.SUCCESS;
			}
			catch (Exception ex)
			{
				Error(ex.Message, ex);
				retCode = RET_CODE.DB_ERROR;
			}
			return retCode;
		}

		public RET_CODE EmailExists(string email)
		{
			RET_CODE retCode;
			try
			{
				retCode = UserDaoProvider.CheckExists(null, email, EXISTS_TYPE.EMAIL) ?
					RET_CODE.EMAIL_EXISTED : RET_CODE.SUCCESS;
			}
			catch (Exception ex)
			{
				Error(ex.Message, ex);
				retCode = RET_CODE.DB_ERROR;
			}
			return retCode;
		}

		public RET_CODE EmailExists(string email, string userId)
		{
			RET_CODE retCode;
			try
			{
				retCode = UserDaoProvider.CheckEmailExists(email, userId) ?
					RET_CODE.EMAIL_EXISTED : RET_CODE.SUCCESS;
			}
			catch (Exception ex)
			{
				Error(ex.Message, ex);
				retCode = RET_CODE.DB_ERROR;
			}
			return retCode;
		}

		public Bitmap GenerateCaptcha(out string captcha)
		{
			captcha = RandomCaptchaProvider.Generate(4);
			return CaptchaProvider.GenerateCaptcha(captcha);
		}

		#endregion

        protected string GeneratePassword()
        {
            return RandomProvider.Generate(_passwordLen);
        }

		protected override Type LogPrefix
		{
			get { return typeof(AccountMembership); }
		}
	}
}
